iT邦幫忙

2022 iThome 鐵人賽

DAY 12
1
自我挑戰組

你也對開始使用typescript感到無力嗎?我也是 - 30天初探typescript系列 第 12

第12天!你有發現我們一直在限縮型別嗎?

  • 分享至 

  • xImage
  •  

我們一直在限縮型別唷

不管是昨天提到的元組跟列舉,還是更早之前的型別別名、介面,我們在做的,一直都是將變數/參數/各類值限縮到特定型別,將我們在編譯時可能發生錯誤的機率降到最低。在第五天的時候,我們有簡單提到型別防衛(Type guard),今天讓我們回過頭來,講講在函式的參數、或函式內部,我們該怎麼進行限縮型別,以達到使用TypeScript的最佳效果。

我們來看個初步的例子吧:

function sayHiXTimes(times: number | string,name:string){
    return "hi".repeat(times) + " " + name
}

已經有了基本的型別概念後,我們可以很快發現,times有可能是數字或字串,而.repeat並非同時存在於兩種型別的方法,所以TypeScript會對我們吼:Argument of type 'string | number' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'

Okok,很合理,既然如此我們就在函式內明確寫個判斷式,如果times的型別是字串就怎樣、不然就怎樣,就能將所有情境處理掉了。

function sayHiXTimes(times: number | string, name: string) {
    if (typeof times === "number") {
        return "hi".repeat(times) + " " + name
    }
    return "hi " + name + " this is wrong arguments"
}

console.log(sayHiXTimes(5, "John"))
// hihihihihi John
console.log(sayHiXTimes("5", "John"))
// hi John this is wrong arguments

這邊寫起來真的超無聊的,我們多寫了一個判斷式,讓整段程式碼變得有些冗長,但這也正是TypeScript的精神。TypeScript會盡量為每一條可能的路徑,去找到他最準確會回傳的值/型別。因此他看到了typeof ...,便知道這邊我們只看其中一條路徑,如果我們只寫完if而沒有寫出第二個return,TypeScript便會告訴我們:Not all code paths return a value.,代表我們沒有考慮到全部的可能,有一條路徑可能沒有回傳值。

typeof只是我們看到的其中一個型別防衛的方法,滿直白好懂的。但我們來看一個會讓他故障的例子:

function printWords(messages: string | string[] | null) {
    if (typeof messages === "string") {
        console.log(messages)
    } else if (typeof messages === "object") {
        messages.forEach(message => console.log(message))
    } else {
        console.log("You gave me nothing!")
    }
}

你看出哪邊有問題了嗎?陣列是objectnull也是object唷!所以在我們想對messages使用陣列特有的方法.forEach時,其實也有可能是對null使用.forEach,想當然就不可能正常運作啦。這邊我們可以再用一個判斷式,來判斷該值存不存在,也就是說,不存在的情況下,我們在操作的就是null,存在的情況下,我們操作的就是陣列或字串。

function printWords(messages: string | string[] | null) {
    if (messages) {
        if (typeof messages === "string") {
            console.log(messages)
        } else if (typeof messages === "object") {
            messages.forEach(message => console.log(message))
        }
    }
    else {
        console.log("here's nothing")
    }
}

以上面這個寫法,我們透過if(messages)messages有沒有值,所以TypeScript覺得我們有考慮到所有路徑了,就不會報錯。

(寫出這種TypeScript的同時,我們還能順便複習一下,到底JavaScript會將什麼值轉譯成false0NaNnullundefined""這類的)。)

Ok,你應該又發現問題了。空字串在上面的例子中,當作參數其實應該也是一個合法的字串吧,但我們在if(messages)裡面,在轉換的過程中會將空字串當作false,所以我們的程式碼執行到了不該執行的地方。

上面這邊講到的是最基本的限縮方式,其實還有更多,像是透過instanceof來判斷某一個值是否來自特定類別衍生出來的、或用in關鍵字來判斷一個物件內有沒有特定的屬性/方法等。但這些觀念都差不多,就是要將型別限制在特定範圍內,好讓開發者提前找到錯誤。

但,這邊還要特別注意,函式內的任何值的型別被限縮後,是可能再轉變成別的型別的,所以我們要更注意值在整個函式內的變化(下面擷取自官網中的範例):

官網上的範例,特地變數的型別一直變動

小結:
你會想開始學TypeScript,上述關於型別防衛、關於限縮,其實你早就知道了。只是TypeScript會再幫我們想的更多一些,畢竟JavaScript作為弱型別的語言,變數型別可能說變就變,今天講的,大概就是讓我們再多思考型別在程式碼中的變化。你說不知道這些會不會怎樣?我覺得差異不大,但多知道一點總是好的,免得哪天TypeScript在檢查你的程式碼時衝康了你,要了解情況就耗時一些。


上一篇
第11天!淺談Tuple 元組 與 Enum 列舉
下一篇
第13天!不用遊樂場了,在本地架設環境吧!
系列文
你也對開始使用typescript感到無力嗎?我也是 - 30天初探typescript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言